package jwt

import (
	"crypto/rand"
	"encoding/base64"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"github.com/google/uuid"
	"server/cfg"
	"server/utils/db"
	"strings"
	"time"
)

const (
	adminTableName = "admins_jwt"
	userTableName  = "users_jwt"
)

func GenerateAccessToken(id int64, username string, isAdmin bool) (string, error) {
	claims := make(jwt.MapClaims)
	claims["id"] = id
	claims["username"] = username
	claims["is_admin"] = isAdmin
	claims["exp"] = time.Now().Add(time.Second * time.Duration(cfg.Config.JWT.AccessTokenExp)).Unix()
	tk := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	secret := generateJWTSecret()
	saveJWTSecret(username, isAdmin, secret)
	token, err := tk.SignedString([]byte(secret))
	if err != nil {
		return "", fmt.Errorf("创建access_token失败：%v", err)
	}
	return token, nil
}

func GetUserInfoFromToken(c *gin.Context) (id int64, username string, isAdmin bool, err error) {
	token := getAccessTokenFromHeader(c)
	if token == "" {
		return 0, "", false, errors.New("请求头未携带token")
	}
	tk, _, e := new(jwt.Parser).ParseUnverified(token, jwt.MapClaims{})
	if e != nil {
		return 0, "", false, fmt.Errorf("解析token失败：%v %s", err, token)
	}
	claims, ok := tk.Claims.(jwt.MapClaims)
	if !ok {
		return 0, "", false, errors.New("无效的token")
	}
	_id, _ := claims["id"].(float64)
	id = int64(_id)
	username, _ = claims["username"].(string)
	isAdmin, _ = claims["is_admin"].(bool)
	return
}

func VerifyAccessToken(c *gin.Context) bool {
	_, username, isAdmin, err := GetUserInfoFromToken(c)
	if err != nil {
		return false
	}
	secret := getJWTSecret(username, isAdmin)
	token := getAccessTokenFromHeader(c)
	if token == "" {
		return false
	}
	tk, err := jwt.Parse(token, func(token *jwt.Token) (any, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, errors.New("签名方法错误")
		}
		return []byte(secret), nil
	})
	if err != nil || !tk.Valid {
		fmt.Println("token已失效：", err)
		return false
	}
	return true
}

func GenerateRefreshToken() (string, error) {
	refreshToken := uuid.New().String()
	db.SetCacheContent(refreshToken, cfg.Config.JWT.RefreshTokenExp)
	return refreshToken, nil
}

func RefreshToken(c *gin.Context, refreshToken string) (newAccessToken, newRefreshToken string, err error) {
	if !db.IsCacheContentValid(refreshToken) {
		err = errors.New("refresh_token 已过期")
		return
	}

	id, username, isAdmin, e := GetUserInfoFromToken(c)
	if e != nil {
		err = e
		return
	}
	newAccessToken, err = GenerateAccessToken(id, username, isAdmin)
	if err != nil {
		newAccessToken = ""
		return
	}
	db.DelCacheContent(refreshToken)
	newRefreshToken, err = GenerateRefreshToken()
	fmt.Println("token已刷新")
	return
}

func DeleteAnywhere(username string, isAdmin bool) {
	table := userTableName
	if isAdmin {
		table = adminTableName
	}
	db.Exec("delete"+" from "+table+" where username=?", username)
}

func generateJWTSecret() string {
	length := 32 // 密钥长度应至少为32字节
	randomBytes := make([]byte, length)
	_, _ = rand.Read(randomBytes)
	return base64.URLEncoding.EncodeToString(randomBytes)
}

func saveJWTSecret(username string, isAdmin bool, secret string) string {
	table := userTableName
	if isAdmin {
		table = adminTableName
	}
	if !db.IsTableExist(table) {
		db.Exec(`create table if not exists ` + table + `(
			id int not null primary key AUTO_INCREMENT,
			username varchar(16) not null default '' unique key,
			jwt_secret varchar(44) not null default '',
			create_time timestamp not null default current_timestamp,
			update_time timestamp not null default current_timestamp on update current_timestamp
		)`)
	}
	if db.IsExist(table, "username=?", username) {
		db.Exec("update "+table+" set jwt_secret=? where username=?", secret, username)
	} else {
		db.Exec("insert"+" into "+table+" (username,jwt_secret) values (?,?)", username, secret)
	}
	return secret
}

func getJWTSecret(username string, isAdmin bool) string {
	table := userTableName
	if isAdmin {
		table = adminTableName
	}
	rows := db.Select("select"+" jwt_secret from "+table+" where username=?", username)
	if len(rows) == 0 {
		return ""
	}
	return rows[0]["jwt_secret"].(string)
}

func getAccessTokenFromHeader(c *gin.Context) string {
	if token := c.GetHeader("Authorization"); token != "" {
		return strings.TrimPrefix(token, "Bearer ")
	}
	return ""
}
